package emulator.hardware.io.peripherals.iec;

import java.io.File;
import java.nio.ByteBuffer;
import java.util.StringTokenizer;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import emulator.util.ByteBufferReader;

public class IecSimFsDriver implements IecSimDevice
{
	static Logger logger = LogManager.getLogger(IecSimFsDriver.class.getName());
	private final static int MAX_CHANNELS = 16;
	private final static int CMD_CHANNEL = 15;
	private final static int NO_CHANNEL = -1;
	private FsDriverChannel channels[] = new FsDriverChannel [MAX_CHANNELS];
	private int _activeChannel = NO_CHANNEL;
	private IecSim _iecSim = null;
	private boolean _isInOpen = false;
	private DriveStatus _driveStatus = new DriveStatus("IEC SIM FS DRIVER V0.2");
	private FsDirectory _directory = new FsDirectory();
	private String _dirPath = null;
	private String _relativePath = "";
	
	public IecSimFsDriver(String dirPath) 
	{
		logger.info("Creating filesystem device for "+dirPath);
		this._dirPath = dirPath;
	}

	public IecSimFsDriver() 
	{
		logger.info("Creating filesystem device for active directory");
		this._dirPath = ".";
	}

	@Override
	public void setIecSim(IecSim iecSim)
	{
		this._iecSim = iecSim;
		resetDevice();
		_directory.load(_dirPath, _relativePath);
	}
	
	private void resetDevice() 
	{
		logger.entry();
		for (int i = 0; i < MAX_CHANNELS; i++)
			closeChannel(i);
		assignChannel(CMD_CHANNEL, new FsDriverCmdChannel(this), true);
		_activeChannel = NO_CHANNEL;
		_driveStatus.setStatus(DriveStatus.RESET, 0, 0);
	}

	private void closeChannel(int channel_no) 
	{
		logger.debug("Closing channel #" + channel_no);
		if (channels[channel_no] != null)
		{
			channels[channel_no].close();
			channels[channel_no] = null;
		}
		if (channel_no == _activeChannel)
			_activeChannel = NO_CHANNEL;
		if (channel_no == CMD_CHANNEL)
			assignChannel(channel_no, new FsDriverCmdChannel(this), false);
	}

	private void assignChannel(int channel_no, FsDriverChannel driver_channel, boolean do_open)
	{
		logger.debug("Assigning channel #" + channel_no + (do_open ? " (open)" : ""));
		channels[channel_no] = driver_channel;
		if (do_open)
			channels[channel_no].open("");
	}

	@Override
	public void processCommand(ByteBuffer data) {
		while (data.hasRemaining())
		{
			int cmd = ByteBufferReader.getByte(data);
			switch (cmd & 0xF0)
			{
			case 0x60: // OPEN CHN/DATA
				openChannel(cmd & 0x0f);
				break;
			case 0xE0: // CLOSE
				close(cmd & 0x0f);
				break;
			case 0xF0: // OPEN
				open(cmd & 0x0f);
				break;
			}
		}
	}

	private void open(int channel) 
	{
		logger.debug("Opening #" + channel);
		switch (channel)
		{
		case CMD_CHANNEL:
			assignChannel(channel, new FsDriverCmdChannel(this), false);
			break;
		default:
			assignChannel(channel, new FsDriverFileChannel(this), false);
			break;
		}
		setActiveChannel(channel);
		_isInOpen = true;
	}

	private void close(int channel)
	{
		logger.debug("Closing #" + channel);
		closeChannel(channel);
	}

	private void openChannel(int channel)
	{
		logger.debug("Opening channel #" + channel);
		setActiveChannel(channel);
		_isInOpen = false;
	}

	public void talk() {
		logger.entry();
		_iecSim.initSend(this);
	}

	public void untalk() {
		logger.entry();
	}

	@Override
	public void processData(ByteBuffer data) {
		if (_isInOpen)
		{
			getActiveChannel().open(ByteBufferReader.toString(data));
			_isInOpen = false;
		}
		else
		{
			if (hasActiveChannel())
				getActiveChannel().write(data);
		}
	}

	@Override
	public void unlisten() {
		logger.entry();
		_isInOpen = false;
	}

	@Override
	public void listen() {
		logger.entry();
		_iecSim.initReceive(this);
	}

	public DriveStatus getDriveStatus() {
		return _driveStatus;
	}

	@Override
	public int read() {
		logger.entry();
		if (hasActiveChannel())
		{
			logger.trace("reading from active channel #"+_activeChannel);
			return getActiveChannel().read();
		}
		else
			logger.warn("No active channel for device "+this);
		return -1;
	}

	private boolean hasActiveChannel() {
		return _activeChannel >= 0 &&
				channels[_activeChannel] != null;
	}

	private FsDriverChannel getActiveChannel() {
		return channels[_activeChannel];
	}

	private void setActiveChannel(int channel) {
		_activeChannel = channel;
	}

	public FsDirectory getDirectory() {
		return _directory;
	}

	@Override
	public void rewind(int offset) {
		if (hasActiveChannel())
			getActiveChannel().rewind(offset);
	}

	public void changeDirectory(String dirSpec)
	{
		if (dirSpec.startsWith(":"))
		{
			doChange(dirSpec.substring(1));
		}
		else if (dirSpec.startsWith("/"))
		{
			int lastIndex = dirSpec.lastIndexOf('/');
			String pathPart = dirSpec.substring(1, lastIndex);
			changeDirectoryTo(pathPart);
		}
		else
		{	// dirSpec == relative path
			doChange(dirSpec);
		}
		_directory.load(_dirPath, _relativePath);
	}

	private void doChange(String path)
	{
		if (path.startsWith("_"))
		{
			int tailDir = _relativePath.lastIndexOf("/");
			if (tailDir < 0)
				_relativePath = "";
			else
				_relativePath = _relativePath.substring(0, tailDir);
		}
		else
			changeDirectoryTo(path);
	}

	private void changeDirectoryTo(String pathPart)
	{
		String currentPath = (pathPart.startsWith("/") ? "" : _relativePath); 
		StringTokenizer tokenizer = new StringTokenizer(pathPart, "/");
	    while (tokenizer.hasMoreTokens())
	    {
	    	String nextDir = tokenizer.nextToken();
	    	if (!nextDir.isEmpty())
	    	{
	    		File currentFile = new File(_dirPath + "/" + currentPath + "/" + nextDir);
	    		if (currentFile.isDirectory())
	    		{
	    			currentPath += "/" + nextDir;
	    		}
	    		else
	    			break;
	    	}
		}
		_relativePath = currentPath;
	}
}
